package com.ustc.actuary.ui;

import java.util.Iterator;
import java.util.Map.Entry;

import org.json.JSONException;
import org.stockchart.StockChartView;
import org.stockchart.StockChartView.HitTestInfo;
import org.stockchart.StockChartView.ITouchEventListener;
import org.stockchart.core.Appearance.Gradient;
import org.stockchart.core.Area;
import org.stockchart.core.Axis;
import org.stockchart.core.Axis.ILabelFormatProvider;
import org.stockchart.core.Axis.Side;
import org.stockchart.core.AxisRange;
import org.stockchart.core.AxisRange.ViewValues;
import org.stockchart.core.Line;
import org.stockchart.indicators.BollingerBandsIndicator;
import org.stockchart.indicators.EmaIndicator;
import org.stockchart.indicators.MacdIndicator;
import org.stockchart.indicators.StochasticIndicator;
import org.stockchart.misc.SmartScaleValuesProvider;
import org.stockchart.points.AbstractPoint;
import org.stockchart.points.BarPoint;
import org.stockchart.points.LinePoint;
import org.stockchart.points.StockPoint;
import org.stockchart.points.StockPoint.PointValue;
import org.stockchart.series.BarSeries;
import org.stockchart.series.LinearSeries;
import org.stockchart.series.SeriesBase;
import org.stockchart.series.StockSeries;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.ViewGroup.LayoutParams;
import android.widget.Toast;

public class KChartView extends Activity {
	
	// i recommend you to start reading from onCreate method and return here 
	// as soon as variables appear

	// we use timer, based on handler callbacks
	private Handler mHandler = new Handler();
	
	// series that we need (we'll initialize them later)
	private StockSeries s1;
	private BarSeries s2;    
	private LinearSeries s3;
    
    // areas 
    private Area a1;
    private Area a2;
    private Area a3;
    // variable to produce unique id's 
    private static long tttt = 0;
    
    // StockChart itself
    StockChartView s;
    
    // this runnable will be called by handler's timeout
	private Runnable mUpdateTimeTask = new Runnable() 
	{
	   public void run() 
	   {
		    // generating stock data
		   
        	double high = 1000 + Math.random()*500;
        	double low = 500+Math.random()*100; 
        	double open = 600 + Math.random()*400;
        	double close = 600 + Math.random()*400;
        	
        	// you can use your class's fields to access areas and series but I search them
        	// every time this callback fired
			Area a1 = s.findAreaByName("A1");
        	Area a2 = s.findAreaByName("A2");
        	
        	StockSeries s1 = (StockSeries)a1.findSeriesByName("stock");
        	BarSeries s2 = (BarSeries)a1.findSeriesByName("volume");
        	LinearSeries s3 = (LinearSeries)a2.findSeriesByName("line");
        	
        	// add point to s1
        	long id =++tttt; 
        	StockPoint ss = new StockPoint();
        	ss.setID(id);
        	ss.setValues(open,high,low,close);
        	s1.getPoints().add(ss);
        	
        	// add point to s2
        	double volume = Math.random()*20000;
        	BarPoint bp = s2.addPoint(0, volume);
        	bp.setID(id);
        	// add point to s3
        	LinePoint lp = s3.addPoint(close);
        	lp.setID(id);
        	
        	// if you want to see the last value, pass something here:
        	s1.setLastValue( close);
        	s2.setLastValue(volume);
        	
        	s3.setLastValue(close);

        	// explicit call recalc() to set auto view values. I know it will be called 
        	// again in onDraw method, but I haven't found better way
        	s.recalc();
        	
        	// Assume we want to move view values to show us the most recent data  
        	// (scroll current view to the most right position). We call one of the overloaded 
        	// methods setViewValues. 
        	if(tttt > 20)
        		s.getGlobalAxisRange(Axis.Side.BOTTOM).setViewValues(ViewValues.SCROLL_TO_LAST);

        	// don't forget to recalc indicators before invalidate
        	s.recalcIndicators();
        	// don't forget to call invalidate when finish
        	s.invalidate();
        	
    		
        	if(tttt < 100)
        	{
        		mHandler.postDelayed(this, 10);        	
        	}       
        	
	   }
	};
	
	protected void onSaveInstanceState (Bundle outState)
	{
		try 
		{
			// if something changes (i.e. orientation) we want to 
			// restore our state later. So, why don't we save it first?
			outState.putString("chart", s.save());
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	// this method is called when only once
	private void newChart()
	{			
		// initializing areas
		
		// first area:
        a1 = new Area();
        a1.setName("A1");
        a1.setTitle("Summer");
        a1.getAppearance().setPrimaryFillColor(Color.rgb(0, 255, 255));
        a1.getAppearance().setSecondaryFillColor(Color.GREEN);
        a1.getAppearance().setGradient(Gradient.LINEAR_VERTICAL);
        a1.getLeftAxis().setVisible(true);
        a1.getLeftAxis().setLinesCount(2);
        a1.getRightAxis().setScaleValuesProvider(new SmartScaleValuesProvider(0));
        // we don't need neither top axis...
        a1.getTopAxis().setVisible(false);
        // ...nor bottom axis!
        a1.getBottomAxis().setVisible(false);
//        a1.setVerticalMargin(0.2f);
        
        // second area:
        a2 = new Area();
        a2.setName("A2");
        a2.getAppearance().setPrimaryFillColor(Color.rgb(255, 143, 0));
        a2.getAppearance().setSecondaryFillColor(Color.rgb(102, 51, 0));
        a2.getAppearance().setGradient(Gradient.LINEAR_VERTICAL);
        a2.setTitle("Autumn");
        // disabling autoheight
        a2.setAutoHeight(false);        
        // setting height explicitly
        a2.setHeightInPercents(0.2f);        
        // we need left axis
        a2.getLeftAxis().setVisible(false);

        // but not the top one
        a2.getTopAxis().setVisible(false);
        
        // third area 
        a3 = new Area();
        a3.setName("A3");
        a3.getAppearance().setPrimaryFillColor(Color.rgb(248, 248, 248));
        a3.getAppearance().setSecondaryFillColor(Color.rgb(204, 255, 255));
        a3.getAppearance().setGradient(Gradient.LINEAR_VERTICAL);
        a3.setTitle("Winter");
        a3.getLeftAxis().setVisible(false);
        // areas done.
        // let's add them to our chart!
        s.getAreas().add(a1);
        s.getAreas().add(a2);             
        s.getAreas().add(a3);        

        
        // creating stock series
    	s1 = new StockSeries();
    	s1.getAppearance().setTextColor(Color.GREEN);
    	// despite each series has unique name, I recommend you to give them names
    	s1.setName("stock");
    	s1.getFallAppearance().setPrimaryFillColor(Color.BLACK);
    	s1.getRiseAppearance().setPrimaryFillColor(Color.YELLOW);
    	// setting outline style and width to make our candlesticks look more attractive. 
    	// i like this combination more than others. I don't recommend setting outline width too
    	// large. 1...3f is enough.      	
//    	s1.setOutlineStyle(OutlineStyle.USE_DARKER_BODY_COLOR);
//    	s1.setOutlineWidth(2.0f);
    	
    	
    	// creating bar series for volume
    	s2 = new BarSeries();
    	s2.setName("volume");    
    	
    	// attaching s2 to left axis
        s2.setYAxisSide(Side.LEFT);
        // change the color of bar series
        s2.getAppearance().setGradient(Gradient.LINEAR_VERTICAL);
        
    	// creating linear series for close prices            	
	    s3 = new LinearSeries();
	    s3.setName("line");
	    s3.setPointsVisible(true);
	    
	    // attaching s3 to right axis
	    s3.setYAxisSide(Side.RIGHT);        
        	    
	    // series done. let's add them to corresponding areas        
        a1.getSeries().add(s2);
        a1.getSeries().add(s1);
        
        a1.getAxis(s1.getYAxisSide()).getAxisRange().setMargin(0.2f);
        
        a2.getSeries().add(s3);
                
        // adding constant lines  (rulers) to the first area just for fun
        a1.getLines().add(new Line(10,Axis.Side.BOTTOM));
        a1.getLines().add(new Line(800,Axis.Side.RIGHT));
        // EMA indicator:
        LinearSeries ema = new LinearSeries();
        a1.getSeries().add(ema);
        s.getIndicators().add(new EmaIndicator(s1,3,ema));
        // The MACD indicator (to know for sure, when to buy or sell :) )
        // For the MACD we (typically) need:
        // 1. Lines x2
        // 2. Histogram x1
        // 3. Own area x1 
        // In theory, you can create only series and attach them to any area you want
        // But i prefer classical representation of the MACD
        // Here we go:       
        LinearSeries macd = new LinearSeries();
        LinearSeries signal = new LinearSeries();
        BarSeries hist = new BarSeries();
        a3.getSeries().add(macd);
        a3.getSeries().add(signal);
        a3.getSeries().add(hist);
        // cool zero level for the histogram
        a3.getLines().add(new Line(0.0,Axis.Side.RIGHT));
        
        // add indicator
        s.getIndicators().add(new MacdIndicator(s1,0,macd,signal,hist));
        
// 		  Bollinger bands indicator example
        
        LinearSeries bbSma = new LinearSeries();
        LinearSeries bbUpper = new LinearSeries();
        LinearSeries bbLower = new LinearSeries();
        a1.getSeries().add(bbSma);
        a1.getSeries().add(bbUpper);
        a1.getSeries().add(bbLower);
        s.getIndicators().add(new BollingerBandsIndicator(s1,3,bbSma,bbUpper,bbLower));
        
        // Stochastic indicator example
        LinearSeries slowK = new LinearSeries();
        slowK.getAppearance().setOutlineColor(Color.BLUE);
        LinearSeries slowD = new LinearSeries();
        
        a3.getSeries().add(slowK);
        a3.getSeries().add(slowD);
        
        s.getIndicators().add(new StochasticIndicator(s1,StockPoint.getValueIndex(PointValue.CLOSE),slowK,slowD));
        
        // the last thing we need, is to make all areas behave as one.
        // we need to set global range
        AxisRange ar = new AxisRange();
        ar.setMovable(true);
        ar.setZoomable(true);
        s.enableGlobalAxisRange(Axis.Side.BOTTOM, ar); 
	}
	
	private void restoreChart(String sss)
	{
		// all we have to do is to restore our chart from JSON.
		// All series created automatically, so we need to find them by name
		try 
		{
			s.load(sss);
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		a1 = s.findAreaByName("A1");
		a2 = s.findAreaByName("A2");
		a3 = s.findAreaByName("A3");
		s1 = (StockSeries)a1.findSeriesByName("stock");
		s2 = (BarSeries)a1.findSeriesByName("volume");
		s3 = (LinearSeries)a1.findSeriesByName("line");
	}
		
	private void wireEvents()
	{
        // we need to wire events each time activity is created.
		// you can provide custom formatter for each axis.
		// for example you may want to format your price with decimals
		
		a1.getRightAxis().setLabelFormatProvider(new ILabelFormatProvider()
		{
		
			@Override
			public String getAxisLabel(Axis sender, double value) {
				return String.valueOf((long)value);
			}
		
		});
			
	   a2.getBottomAxis().setLabelFormatProvider(new ILabelFormatProvider() 
        {
			@Override
			public String getAxisLabel(Axis sender, double value) 
			{			
				// here you are formatting the X axis. 
				// it is used to display time. But I don't have time in my example,
				// but you can set time as ID of each point.
				Area a = sender.getParent();
				
				for(int i=0;i<a.getSeries().size();i++)
				{
					SeriesBase s = a.getSeries().get(i);
					
					int index = s.convertToArrayIndex(value);
					if(index >=0 && index < s.getPointCount())
					{
						Object id = ((AbstractPoint)s.getPointAt(index)).getID();
						
						if(null != id)
							return id.toString();
					}
				}
				
				return null;
			}
        	
        });
	   
	   a3.getBottomAxis().setLabelFormatProvider(new ILabelFormatProvider()
		{
		
			@Override
			public String getAxisLabel(Axis sender, double value) 
			{
				return "";
			}
		});
		
	   s.setTouchEventUpListener(new ITouchEventListener()
	   {
		@Override
		public void onTouchEvent(MotionEvent e) 
		{
				HitTestInfo i = s.getHitTestInfo(e.getX(), e.getY());
				
				String s = "nothing";
				if(null != i.element)
				{
					s=i.element.toString();
				
					 Iterator<Entry<String,AbstractPoint>> it = i.points.entrySet().iterator();
					 while(it.hasNext())
					 {
						 Entry<String,AbstractPoint> p = it.next();
						 if(p.getValue().getID() != null)
							 s+="["+p.getKey()+": "+p.getValue().getID().toString()+"]\n";
					 }
				}
				
				Toast.makeText(KChartView.this,s, Toast.LENGTH_SHORT).show();
		}
		   
	   });

	}
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // create new black stock chart
        s = new StockChartView(this);
                             
        if(null != savedInstanceState)
        {
        	// if we have something to restore, then restore it
        	 String sss = savedInstanceState.getString("chart");
        	 restoreChart(sss);        
        }
        else
        {
        	// if we have nothing to restore (i.e. first launch) then initialize
        	newChart();
        }

        wireEvents();


        setContentView(s, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
              
        // timer
        mHandler.removeCallbacks(mUpdateTimeTask);
        mHandler.postDelayed(mUpdateTimeTask, 100);
    }
}

// THE END!